package com.zpy.playground.redisTest.lettuceConfig;

import io.lettuce.core.ClientOptions;
import io.lettuce.core.SocketOptions;
import io.lettuce.core.cluster.ClusterClientOptions;
import io.lettuce.core.cluster.ClusterTopologyRefreshOptions;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisNode;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Component;

import java.time.Duration;
import java.util.ArrayList;
import java.util.List;

@Component
@Configuration
public class RedisTemplateClusterConfiguration {
    @Bean(name = "clusterRedisTemplate")
    public RedisTemplate<String, Object> redisTemplate(
        @Qualifier("clusterConnectionFactory") LettuceConnectionFactory connectionFactory) {
        // 配置redisTemplate
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(connectionFactory);
        redisTemplate.setKeySerializer(new StringRedisSerializer());//key序列化
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());//value序列化
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());//key序列化
        redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer()); // value序列化方式采用jackson
        return redisTemplate;
    }

    @Primary
    @Bean(name = "clusterConnectionFactory")
    public LettuceConnectionFactory connectionFactory(RedisClusterDayuConfiguration config,
        @Qualifier("clusterPoolingClientConfig") LettucePoolingClientConfiguration clientConfiguration) {
        List<RedisNode> redisNodes = new ArrayList<>();
        for (String node : config.getNodes()) {
            String[] split = node.split(":");
            redisNodes.add(new RedisNode(split[0], Integer.parseInt(split[1])));
        }

        // RedisClusterClient redisClusterClient = RedisClusterClient.create(redisURIList);
        RedisClusterConfiguration clusterConfiguration = new RedisClusterConfiguration();
        clusterConfiguration.setMaxRedirects(config.getMaxRedirects());
        clusterConfiguration.setClusterNodes(redisNodes);

        // 因为有代理,所以不需要装配clientConfiguration
        LettuceConnectionFactory lettuceConnectionFactory =
            new LettuceConnectionFactory (
                clusterConfiguration
            );

        // Enables multiple LettuceConnections to share a single native connection. If set to false, every operation on LettuceConnection will open and close a socket.
        // lettuceConnectionFactory.setShareNativeConnection(false);

        return lettuceConnectionFactory;
    }

    @Bean(name = "clusterPoolingClientConfig")
    public LettucePoolingClientConfiguration clientConfiguration(
        RedisClusterDayuConfiguration config,
        @Qualifier("clusterGenericPoolConfig") GenericObjectPoolConfig poolConfig) {
        SocketOptions socketOptions = SocketOptions.builder().connectTimeout(Duration.ofMillis(config.getTimeout())).build();

        ClusterTopologyRefreshOptions topologyRefreshOptions = ClusterTopologyRefreshOptions.builder()
            .enableAllAdaptiveRefreshTriggers()
            .enablePeriodicRefresh(Duration.ofMillis(config.getRedisClusterTopologyRefreshPeriodMillis()))
            .build();

        ClusterClientOptions clientOptions = ClusterClientOptions.builder()
            .autoReconnect(true)
            .pingBeforeActivateConnection(true)
            .cancelCommandsOnReconnectFailure(false)
            .disconnectedBehavior(ClientOptions.DisconnectedBehavior.ACCEPT_COMMANDS)
            .socketOptions(socketOptions)
            .topologyRefreshOptions(topologyRefreshOptions)
            .build();

        return LettucePoolingClientConfiguration.builder()
                .commandTimeout(Duration.ofMillis(200))
                .shutdownTimeout(Duration.ZERO)
                .clientOptions(clientOptions)
                .poolConfig(poolConfig)
                .build();
    }

    @Bean(name = "clusterGenericPoolConfig")
    public GenericObjectPoolConfig genericObjectPoolConfig(RedisClusterDayuConfiguration config) {
        GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
        poolConfig.setMaxIdle(config.getLettuce().getPool().getMaxIdle());
        poolConfig.setMinIdle(config.getLettuce().getPool().getMinIdle());
        poolConfig.setMaxWait(Duration.ofMillis(config.getLettuce().getPool().getMaxWait()));
        poolConfig.setMaxTotal(config.getLettuce().getPool().getMaxActive());
        //连接池耗尽后是否需要等待, 默认true表示等待. 当值为true时, setMaxWait才会生效
        poolConfig.setBlockWhenExhausted(true);
        //连接池耗尽后获取连接的最大等待时间, 默认-1表示一直等待
        poolConfig.setMaxWait(Duration.ofMillis(1000));
        //创建连接时校验有效性(ping)，默认false
        poolConfig.setTestOnCreate(false);
        //获取连接时校验有效性(ping), 默认false, 业务量大时建议设置为false减少开销
        poolConfig.setTestOnBorrow(true);
        //归还连接时校验有效性(ping)，默认false, 业务量大时建议设置为false减少开销
        poolConfig.setTestOnReturn(false);
        //是否开启空闲连接检测，如为false，则不剔除空闲连接
        poolConfig.setTestWhileIdle(true);
        //禁止最小空闲时间关闭连接
        poolConfig.setMinEvictableIdleTime(Duration.ofMillis(-1));
        //连接空闲多久后逐出，当空闲时间>该值，并且空闲连接>最大空闲数时直接逐出，不再根据MinEvictableIdleTimeMillis判断（默认逐出策略）
        poolConfig.setSoftMinEvictableIdleTime(Duration.ofMillis(1000));
        //空闲连接逐出的检测周期，默认为60s
        poolConfig.setTimeBetweenEvictionRuns(Duration.ofMillis(1000));
        return poolConfig;
    }
}
